﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.IO;
using System.Security.Cryptography;
using ESpace;
using ElementClasses;

namespace Organizer
{
    public partial class FormService : Form
    {
        public DIR ReadData = null;

        FormMain frmMain;
        object locker;

        public class MySqlCommOneTransaction : IDisposable
        {
            MySql.Data.MySqlClient.MySqlConnection conn;
            MySql.Data.MySqlClient.MySqlCommand cmd;

            bool bCommit = false;
            string connString;

            public String CommandText { get { return cmd.CommandText; } set { cmd.CommandText = value; } }

            public Dictionary<string, object> Parameters = new Dictionary<string, object>();

            void SetParameters()
            {
                cmd.Parameters.Clear();
                foreach (KeyValuePair<string, object> prm in Parameters)
                    cmd.Parameters.AddWithValue(prm.Key, prm.Value);
            }

            public void ExecuteNonQuery() { SetParameters(); cmd.ExecuteNonQuery(); }

            public DataRowCollection ExecuteReader()
            {
                SetParameters();

                MySql.Data.MySqlClient.MySqlDataAdapter da = new MySql.Data.MySqlClient.MySqlDataAdapter(cmd);
                DataSet ds = new DataSet();
                da.Fill(ds);
                DataTable ret = ds.Tables[0];
                ds.Dispose();
                return ret.Rows;
            }

            public object ExecuteScalar()
            {
                SetParameters();
                return cmd.ExecuteScalar();
            }

            public MySqlCommOneTransaction(string connString)
            {
                this.connString = connString;
            }

            public void Open()
            {
                conn = new MySql.Data.MySqlClient.MySqlConnection(connString);
                conn.Open();
                cmd = new MySql.Data.MySqlClient.MySqlCommand();
                cmd = conn.CreateCommand();
                cmd.Transaction = conn.BeginTransaction();
            }

            public void CommitAndClose()
            {
                bCommit = true;
                Dispose();
            }

            public void Dispose()
            {
                if (cmd != null)
                {
                    if (bCommit)
                    {
                        try { cmd.Transaction.Commit(); }
                        catch { /*MessageBox.Show("cmd.Transaction.Commit() 1");*/ }
                    }
                    else
                    {
                        try { cmd.Transaction.Rollback(); }
                        catch { /*MessageBox.Show("cmd.Transaction.Rollback() 1");*/ }
                    }

                    try { cmd.Dispose(); }
                    catch { /*MessageBox.Show("cmd.Transaction.Rollback() 2");*/ }

                    cmd = null;
                }
                if (conn != null)
                {
                    try { conn.Close(); }
                    catch { /*MessageBox.Show("cmd.Transaction.Close() 1");*/ }
                    try { conn.Dispose(); }
                    catch { /*MessageBox.Show("cmd.Transaction.Dispose() 1");*/ }

                    conn = null;
                }
            }
        }

        /// <summary>None - ничего, Run - идут коммуникации с БД, Reqest_OK - удачное завершение, Request_Err - произошла ошибка </summary>
        enum ActionStatus { None, Run, Request_OK, Request_Error }

        ActionStatus aStat;
        string ActionErrorMessage = "";

        Thread sqlTh = null;
        DateTime dtTimeOut = DateTime.MaxValue;

        delegate void CommunicationDelegate(MySqlCommOneTransaction cmd, object CommFunctionData, string userID, out object ResultData);
        public delegate void ResultDelegate(object CommFunctionData, object ResultData, bool ActionIsOK);

        /// <summary>Данные, передаваемые в поток</summary>
        class SQLThreadData
        {
            public string ConnString;
            public string Service_Login;
            public string Service_MD5Pass;
            public object CommFunctionData;
            public CommAction CommAct;
            public SQLThreadData(string ConnString, string Service_Login, string Service_MD5Pass, CommAction CommAct)
            {
                this.ConnString = ConnString;
                this.Service_Login = Service_Login;
                this.Service_MD5Pass = Service_MD5Pass;
                this.CommAct = CommAct;
            }
        }

        /// <summary>Данные по ожидающему коммуникаций дейсткию</summary>
        class CommAction
        {
            public object CommFunctionData;
            public CommunicationDelegate CommFunction; // функция некоторой работы с БД после логина
            public object ResultData = null; // некоторый результат выполнения CommFunction (заполняется в результате выполнения CommFunction)
            public ResultDelegate ResultFunction; // функция, вызывающаяся по завершении выполнения CommFunction. Вызывается уже в основном потоке
            public CommAction(CommunicationDelegate CommFunction, object CommFunctionData, ResultDelegate ResultFunction)
            {
                this.CommFunctionData = CommFunctionData;
                this.CommFunction = CommFunction;
                this.ResultFunction = ResultFunction;
            }
        }

        /// <summary>Ожидающие коммуникаций дейсткия</summary>
        List<CommAction> CommActionList = new List<CommAction>();

        public FormService(FormMain frmMain)
        {
            InitializeComponent();
            this.frmMain = frmMain;
            locker = new object();
        }

        private void FormService_Shown(object sender, EventArgs e)
        {
            pnlSQL.Enabled = true;

            txbSqlUrl.Text = Gl.sett.Sql_URL;
            txbSqlDBName.Text = Gl.sett.Sql_DBName;
            txbSqlLogin.Text = Gl.sett.Sql_Login;
            txbSqlPass.Text = "**********";
            txbSqlPass.Modified = false;

            txbLogin.Text = Gl.sett.Login;
            txbPass.Text = "**********";
            txbPass.Modified = false;

            btnClose.Focus();
        }

        #region *** Коммуникации *******************************************************************

        // Таймер последовательного выполнения дуйствий по коммуникациям с БД
        private void timer1_Tick(object sender, EventArgs e)
        {
            tmr.Stop();
            try
            {
                // если идут коммуникации, выходим
                if (aStat == ActionStatus.Run)
                {
                    if (sqlTh != null)
                    {
                        if (sqlTh.ThreadState == ThreadState.Running)
                        {
                            if (DateTime.Now >= dtTimeOut)
                            {
                                Gl.ConnState = Gl.ConnectState.OffLine;
                                Gl.AddDebugLog("SQL -> Время ожидания завершения коммуникаций с БД истекло", true);
                                aStat = ActionStatus.None;
                                CommActionList[0].ResultFunction(CommActionList[0].CommFunctionData, null, false);
                            }
                            tmr.Start();
                            return;
                        }
                    }
                    else throw new Exception("SQL -> Поток равен null при статусе соединения 'Query'");
                }
                // если пришел какой-то ответ после коммуникаций, делаем что-то и сбрасываем результат
                else if (aStat != ActionStatus.None)
                {
                    if (aStat == ActionStatus.Request_Error)
                    {
                        Gl.ConnState = Gl.ConnectState.OffLine;
                        Gl.AddDebugLog("SQL -> Ошибка соединения или функционала: " + ActionErrorMessage, true);
                        CommActionList[0].ResultFunction(CommActionList[0].CommFunctionData, null, false);
                    }
                    else
                    {
                        CommActionList[0].ResultFunction(CommActionList[0].CommFunctionData, CommActionList[0].ResultData, true);
                        Gl.ConnState = Gl.ConnectState.OnLine;
                    }

                    aStat = ActionStatus.None;

                    CommActionList.RemoveAt(0);
                }
                // если коммуникаций нет (т.е. можно приступать к выполнению следующего действия)
                else
                {
                    if (CommActionList.Count > 0)
                    {
                        Gl.ConnState = Gl.ConnectState.Sync;
                        dtTimeOut = DateTime.Now.AddSeconds(10);
                        aStat = ActionStatus.Run;
                        sqlTh = new Thread(new ParameterizedThreadStart(StartResponse));
                        sqlTh.Start(new SQLThreadData(Gl.sett.SqlConnString, Gl.sett.Login, Gl.sett.PassMd5, CommActionList[0]));
                    }
                }

            }
            catch (Exception ex)
            {
                Gl.ConnState = Gl.ConnectState.OffLine;
                Gl.AddDebugLog("Ошибка в коде: " + ex.Message + "\r\nТрассировка: " + ex.StackTrace, true);

                if (CommActionList.Count > 0)
                {
                    CommActionList[0].ResultFunction(CommActionList[0].CommFunctionData, null, false);
                    CommActionList.RemoveAt(0);
                }
            }

            if (CommActionList.Count > 0) tmr.Start();
            else dtTimeOut = DateTime.MaxValue;
        }

        void SQLRequestSync(CommAction CommAct)
        {
            CommActionList.Add(CommAct);
            if (!tmr.Enabled) tmr.Start();
        }

        void StartResponse(object o)
        {
            SQLThreadData rData = (SQLThreadData)o;

            MySqlCommOneTransaction cmd = new MySqlCommOneTransaction(rData.ConnString);
            try { cmd.Open(); }
            catch (Exception ex)
            {
                lock (locker) { aStat = ActionStatus.Request_Error; }
                ActionErrorMessage = "Ошибка соединения с БД: " + ex.Message + "\r\n Трассировка: " + ex.StackTrace;
                return;
            }

            try
            {
                // проверка логина
                string userID = LoginInServer(cmd, rData.Service_Login, rData.Service_MD5Pass);
                if (userID == "") throw new Exception("Неверный логин или пароль");

                // вызов фии коммуникаций
                rData.CommAct.CommFunction(cmd, rData.CommAct.CommFunctionData, userID, out rData.CommAct.ResultData);

                // подтверждение транзакции и закрытие коннекта
                cmd.CommitAndClose();

                lock (locker) { aStat = ActionStatus.Request_OK; }
            }
            catch (Exception ex)
            {
                lock (locker) { aStat = ActionStatus.Request_Error; }
                ActionErrorMessage = "Ошибка: " + ex.Message + "\r\n Трассировка: " + ex.StackTrace;
            }
            finally
            {
                // если еще не закрыто, то Rollback и закрытие
                cmd.Dispose();
            }

        }

        string LoginInServer(MySqlCommOneTransaction cmd, string Login, string MD5Pass)
        {
            cmd.CommandText = "SELECT pass, row_id FROM orgz_users WHERE login = '" + Login + "'";
            DataRowCollection rdr = cmd.ExecuteReader();
            if (rdr.Count != 1) { BuildErrInThread("Ошибка логина. Не найден юзер"); cmd.Dispose(); return ""; }
            if (rdr[0]["pass"].ToString().ToUpper() != ESpace.PHPService.GetMD5(MD5Pass).ToUpper()) { BuildErrInThread("Ошибка логина. Неверный пароль"); cmd.Dispose(); return ""; }
            return rdr[0]["row_id"].ToString();
        }

        void BuildErrInThread(string errStr)
        {
            lock (locker)
            {
                aStat = ActionStatus.Request_Error;
                ActionErrorMessage = errStr;
            }
        }

        string ToBase64(String str) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(str)); }
        string FromBase64(String str64) { return Encoding.UTF8.GetString(Convert.FromBase64String(str64)); }

        /// <summary> Смена Пароля </summary>
        private void btnChangePass_Click(object sender, EventArgs e)
        {
            FormChangePass frmChangePass = new FormChangePass();
            frmChangePass.ShowDialog(new FormChangePass.ChangePassDelegate(ChangePass));
            frmChangePass.Dispose();
        }

        bool ChangePass(string newPass)
        {
            // проверка корректности нового пароля
            if (!StringIsCorrect(newPass))
            {
                MessageBox.Show("В пароле содержатся недопустимые символы");
                return false;
            }

            MySqlCommOneTransaction cmd = new MySqlCommOneTransaction(Gl.sett.SqlConnString);
            try { cmd.Open(); }
            catch { MessageBox.Show("Невозможно соединиться с Базой данных"); cmd.Dispose(); return false; }

            try
            {
                string userID;

                #region *** проверка логина ********************

                userID = LoginInServer(cmd, Gl.sett.Login, Gl.sett.PassMd5);
                if (userID == "")
                {
                    MessageBox.Show("Ошибка логина");
                    cmd.Dispose();
                    return false;
                }

                #endregion *** проверка логина ********************

                string newMD5Pass = ESpace.PHPService.GetMD5(newPass);

                // установка нового пароля
                cmd.Parameters.Clear();
                // сохраняем двойной md5
                cmd.CommandText = "update orgz_users set pass='" + ESpace.PHPService.GetMD5(newMD5Pass) + "' where row_id='" + userID + "'";
                cmd.ExecuteNonQuery();
                cmd.CommitAndClose();

                Gl.sett.PassMd5 = newMD5Pass;
                txbPass.Text = newMD5Pass;
                txbPass.Modified = false;
                Gl.sett.Save();
                return true;

            }
            catch (Exception ex)
            {
                aStat = ActionStatus.Request_Error;
                ActionErrorMessage = "Ошибка смены пароля \r\nТекст: " + ex.Message + "\r\n Трассировка: " + ex.StackTrace;
                return false;
            }
            finally
            {
                cmd.Dispose();
            }
        }

        #endregion *** Коммуникации *******************************************************************

        #region *** Коммуникации для внутренних элементов *******************************************************************

        /// <summary>Данные, передаваемые для функции коммуникации с БД (для внутренних элементов)</summary>
        class ElmDBSyncData
        {
            public long LocalVertion;
            public List<string> DeleteList = new List<string>();
            public List<string> EditElList = new List<string>();
            public List<string> EditSrsDataList = new List<string>();

            public bool AllReWrite = false;
        }

        enum ElmDBResultStat { NewVertion, VertionOK, WriteOK, NeedAllWrite }

        /// <summary>Данные результата для функции коммуникации с БД (для внутренних элементов)</summary>
        class ElmDBResultData
        {
            public ElmDBResultStat stat;
            public DIR ServDir;
            public long NewServVer;
        }

        /// <summary>Подготовка и запуск синхронизации</summary>
        /// <param name="NetDir">Сетевой каталог</param>
        /// <param name="AllRewrite">Перезаписать все</param>
        public void RequestSinc(DIR NetDir, bool AllRewrite)
        {
            ElmDBSyncData syncData = new ElmDBSyncData();
            syncData.LocalVertion = Gl.ServVertion;
            syncData.AllReWrite = AllRewrite;

            #region *** внутренние элементы *****************************************

            List<DBItem> itmL = Gl.GetAllItems(NetDir);

            for (int i = 0; i < itmL.Count; i++)
            {
                Element el = (Element)itmL[i];
                if (el.NetStat == NetStatus.NotActual || AllRewrite)
                {
                    el.NetStat = NetStatus.Write;
                    Gl.AddDebugLog("Установка Write для " + el.FullPath, false);

                    string Path = Gl.GetOtnPath(Gl.SystemDir.Net, el.FullPath) + " ";
                    syncData.EditElList.Add(Path);
                    syncData.EditSrsDataList.Add(el.SourceData);
                }
            }

            // удаленные
            for (int i = 0; i < Gl.DelList.Count; i++)
            {
                string Path = Gl.GetOtnPath(Gl.SystemDir.Net, Gl.DelList[i]) + " ";
                syncData.DeleteList.Add(Path);
                Gl.AddDebugLog("Установка Delete для " + Gl.DelList[i], false);
            }

            #endregion *** внутренние элементы *****************************************

            Gl.AddDebugLog("SQL запрос на синхронизацию", false);
            if(AllRewrite)SQLRequestSync(new CommAction(new CommunicationDelegate(CommFunction_ReWriteAll), syncData, new ResultDelegate(CommFunction_EndReWriteAll)));
            else SQLRequestSync(new CommAction(new CommunicationDelegate(CommFunction_SincInnerElm), syncData, new ResultDelegate(CommFunction_EndSincInternalData)));
        }

        // добавление элемента в базу по указанному пути (если директорий в базе нет, они создаются)
        void AddElementInDB(DIR db, string FullPath, string SourceData, string Ver)
        {
            string[] sl = FullPath.Split('\\');
            DIR Parent = db;
            // находим родительскую директорию, если ее еще нет
            for (int i = 0; i < sl.Length - 1; i++)
            {
                int ind = Parent.IndexOfRecord(sl[i]);
                // если директории нет, создаем ее
                if (ind == -1)
                {
                    NameExt NmExtDir = new NameExt(sl[i]);
                    DIR newDir = new DIR(NmExtDir.Name, Parent, NetStatus.Actual);
                    Parent.Items.Add(newDir);
                    ind = Parent.Items.Count - 1;
                }

                Parent = (DIR)Parent.Items[ind];
            }

            NameExt NmExt = new NameExt(sl[sl.Length - 1]);

            // добавляем элемент в директорию
            Element el = Gl.unL[NmExt.Ext].CreateElementBySourceDate(NmExt.Name, Parent, SourceData);
            el.NetStat = NetStatus.Actual;
            el.bSinh = true;
            el.ServVertion = Convert.ToInt64(Ver);
            Parent.Items.Add(el);
        }

        struct NameExt
        {
            public string Name;
            public string Ext;
            public NameExt(string NmExt)
            {
                int ind1 = NmExt.LastIndexOf(".");
                if (ind1 == -1) throw new Exception("неверный путь к элементу " + NmExt);
                Ext = NmExt.Substring(ind1 + 1, NmExt.Length - ind1 - 1);
                Name = NmExt.Substring(0, ind1);
            }
        }

        #region *** Для коммуникаций ************************

        void CommFunction_SincInnerElm(MySqlCommOneTransaction cmd, object CommFunctionData, string userID, out object ResultData)
        {
            ElmDBSyncData cData = (ElmDBSyncData)CommFunctionData;
            ElmDBResultData rData = new ElmDBResultData();

            // изначальная серверная версия до каких-либо лействий
            long ServVertion = GetActServVertion(cmd, userID);
            // текущая серверная версия
            long ActServVertion = ServVertion;

            #region *** версия сервера старше ******************
            if (ServVertion < cData.LocalVertion)
            {
                lock (locker)
                {
                    rData.stat = ElmDBResultStat.NeedAllWrite;
                    rData.NewServVer = ActServVertion;
                }
                ResultData = rData;
                return;
            }
            #endregion *** версия сервера старше ******************

            // удаляем необходимые записи независимо от того, новее ли версия сервера
            #region *** Удаление *********************

            // (удаление всех записей, которые более не существуют на клиенте. Не зависимо от версии они удаляются и на сервере)
            if (cData.DeleteList.Count > 0)
            {
                cmd.Parameters.Clear();
                string delVals = "";
                for (int i = 0; i < cData.DeleteList.Count; i++)
                {
                    if (i != 0) delVals += ", ";
                    delVals += "@Del" + i.ToString();
                    cmd.Parameters.Add("@Del" + i.ToString(), ToBase64(cData.DeleteList[i]));
                }
                cmd.CommandText = "delete from orgz_items where user_id=" + userID + " and full_name in (" + delVals + ")";
                cmd.ExecuteNonQuery();

                // --- запись новой версии ---------------
                ActServVertion = BuildNewVertion(cmd, userID, ActServVertion);
            }

            #endregion *** Удаление *********************

            #region *** версия сервера новее ******************
            if (ServVertion > cData.LocalVertion)
            {
                // формируем каталог
                DIR newDir = new DIR("", null, NetStatus.Local);
                cmd.Parameters.Clear();
                cmd.CommandText = "select Full_Name, Source_Data, Ver from orgz_items where user_id=" + userID + " order by full_name";
                DataRowCollection rdr = cmd.ExecuteReader();
                foreach (DataRow row in rdr)
                {
                    string s1 = FromBase64(row["Full_Name"].ToString().Trim()).Trim();
                    string s2 = row["Source_Data"].ToString().Trim();
                    string s3 = row["Ver"].ToString().Trim();
                    AddElementInDB(newDir, s1, s2, s3);
                }

                rData.ServDir = newDir;
                rData.stat = ElmDBResultStat.NewVertion;
                rData.NewServVer = ActServVertion;

                ResultData = rData;
                return;
            }
            #endregion *** версия сервера новее ******************

            #region *** запись новых данных ********************

            // если есть что добавлять/обновлять
            if (cData.EditElList.Count > 0)
            {
                ActServVertion = BuildNewVertion(cmd, userID, ActServVertion);

                // внутренние элементы
                for (int i = 0; i < cData.EditElList.Count; i++)
                    AddElementToServer(cmd, userID, ToBase64(cData.EditElList[i]), cData.EditSrsDataList[i], ActServVertion);
            }

            // если что-то добавлялось, удалялось, менялось
            if (cData.DeleteList.Count > 0 || cData.EditElList.Count > 0)
            {
                rData.stat = ElmDBResultStat.WriteOK;
                rData.NewServVer = ActServVertion;

                ResultData = rData;
                return;
            }

            #endregion *** запись новых данных ********************

            // версия актуальна
            rData.stat = ElmDBResultStat.VertionOK;
            rData.NewServVer = ActServVertion;

            ResultData = rData;
        }

        void AddElementToServer(MySqlCommOneTransaction cmd, string userID, string elBase64FName, string sourceData, long elVer)
        {
            //long elVer = GetActServVertionElement(cmd, userID, elBase64FName);

            cmd.Parameters.Clear();
            cmd.CommandText = "delete from orgz_items where user_id=" + userID + " and full_name = '" + elBase64FName + "'";
            cmd.ExecuteNonQuery();

            cmd.Parameters.Clear();
            cmd.CommandText = "insert into orgz_items (user_id, Full_Name, source_data, Ver) values(" + userID + ", @Full_Name, @source_data, @Ver)";
            cmd.Parameters.Add("@Full_Name", elBase64FName);
            cmd.Parameters.Add("@source_data", sourceData);
            cmd.Parameters.Add("@Ver", elVer);
            cmd.ExecuteNonQuery();
        }

        long GetActServVertion(MySqlCommOneTransaction cmd, string userID)
        {
            cmd.Parameters.Clear();
            cmd.CommandText = "select val from orgz_settings where key_id='vertion' and user_id=" + userID;
            DataRowCollection rdr = cmd.ExecuteReader();
            long ret = 0;
            if (rdr.Count == 1) ret = Convert.ToInt64(rdr[0]["val"].ToString());
            return ret;
        }

        long BuildNewVertion(MySqlCommOneTransaction cmd, string userID, long actVer)
        {
            cmd.Parameters.Clear();
            cmd.CommandText = "delete from orgz_settings where key_id='vertion' and user_id=" + userID;
            cmd.ExecuteNonQuery();
            cmd.CommandText = "insert into orgz_settings (val, key_id, user_id) values('" + (actVer + 1) + "','vertion', " + userID + ")";
            cmd.ExecuteNonQuery();

            return GetActServVertion(cmd, userID);
        }

        #endregion *** Для коммуникаций ************************

        void CommFunction_EndSincInternalData(object CommFunctionData, object ResultData, bool ActionIsOK)
        {
            if (!ActionIsOK)
            {
                Gl.ResetWriteStatus(false);
                frmMain.EndSinhronize(Gl.SinhronizeResult.ERROR);
                Gl.SaveDB();
            }
            else
            {
                ElmDBResultData rData = (ElmDBResultData)ResultData;
                switch (rData.stat)
                {
                    case ElmDBResultStat.NewVertion:
                        {
                            Gl.AddDebugLog("SQL -> Пришел ответ: Требуется синхронизация", false);
                            Gl.ResetWriteStatus(false);
                            Gl.IsActualFlag = false;
                            // возводим ожидание синхронизации
                            Gl.SyncData = new SyncDataInEl(rData.ServDir, rData.NewServVer);
                            break;
                        }
                    case ElmDBResultStat.VertionOK:
                        {
                            Gl.AddDebugLog("SQL -> Пришел ответ: Версия Актуальна", false);
                            Gl.IsActualFlag = true;
                            frmMain.EndSinhronize(Gl.SinhronizeResult.VERTION_OK);
                            break;
                        }
                    case ElmDBResultStat.WriteOK:
                        {
                            Gl.AddDebugLog("SQL -> Пришел ответ: Запись успешна", false);
                            Gl.DelList.Clear();
                            Gl.ServVertion = rData.NewServVer;
                            Gl.ResetWriteStatus(true);
                            Gl.IsActualFlag = true;
                            frmMain.EndSinhronize(Gl.SinhronizeResult.WRITE_OK);
                            Gl.SaveDB();
                            break;
                        }
                    case ElmDBResultStat.NeedAllWrite:
                        {
                            Gl.AddDebugLog("SQL -> Пришел ответ: Требуется полная перезапись", true);
                            Gl.ResetWriteStatus(false);
                            Gl.SaveDB();

                            Gl.AddDebugLog("Запрос на полную перезапись данных на сервере", true);
                            RequestSinc(Gl.GetSystemDir(Gl.SystemDir.Net), true);

                            break;
                        }
                }
            }
        }

        #endregion *** Коммуникации для внутренних элементов *******************************************************************

        #region *** Коммуникации для полной перезаписи внутренних элементов на сервере *******************************************************************

        /// <summary>Данные результата для функции коммуникации с БД (для внутренних элементов)</summary>
        class ElmDBResultReWrite
        {
            public long NewServVer;
        }

        void CommFunction_ReWriteAll(MySqlCommOneTransaction cmd, object CommFunctionData, string userID, out object ResultData)
        {
            ElmDBSyncData cData = (ElmDBSyncData)CommFunctionData;
            ElmDBResultReWrite rData = new ElmDBResultReWrite();

            // изначальная серверная версия до каких-либо лействий
            long ServVertion = GetActServVertion(cmd, userID);
            // текущая серверная версия
            long ActServVertion = ServVertion;

            #region *** удаление *********************

            cmd.Parameters.Clear();
            cmd.CommandText = "delete from orgz_items where user_id=" + userID;
            cmd.ExecuteNonQuery();

            #endregion *** запись удаление *********************

            #region *** запись новых данных ********************

            // генерим новую версию, относительно локальной
            ActServVertion = BuildNewVertion(cmd, userID, cData.LocalVertion);

            // внутренние элементы
            for (int i = 0; i < cData.EditElList.Count; i++)
                AddElementToServer(cmd, userID, ToBase64(cData.EditElList[i]), cData.EditSrsDataList[i], ActServVertion);

            #endregion *** запись новых данных ********************

            rData.NewServVer = ActServVertion;

            ResultData = rData;
            return;

        }
        
        void CommFunction_EndReWriteAll(object CommFunctionData, object ResultData, bool ActionIsOK)
        {
            if (!ActionIsOK)
            {
                Gl.ResetWriteStatus(false);
                frmMain.EndSinhronize(Gl.SinhronizeResult.ERROR);
            }
            else
            {
                ElmDBResultReWrite rData = (ElmDBResultReWrite)ResultData;

                Gl.DelList.Clear();
                Gl.ServVertion = rData.NewServVer;
                Gl.ResetWriteStatus(true);
                Gl.IsActualFlag = true;
                frmMain.EndSinhronize(Gl.SinhronizeResult.WRITE_OK);
                Gl.SaveDB();
            }
        }
               
        #endregion *** Коммуникации для полной перезаписи внутренних элементов на сервере *******************************************************************

        #region *** Внелементы **********************************

        /// <summary>Подготовка и запуск синхронизации для внелемента</summary>
        public void RequestSincExElm(bool CheckVertion, ExtElementUnit exU, ResultDelegate CommFunction_Result)
        {
            Gl.AddDebugLog("SQL запрос на синхронизацию для внелемента '"+exU.ExElm.Name+"'", false);
            SQLRequestSync(new CommAction(new CommunicationDelegate(CommFunction_SincExtElm), new ExElmCommFunctionData(CheckVertion, exU), new ResultDelegate(CommFunction_Result)));
        }

        public class ExElmCommFunctionData
        {
            public bool CheckVertion;
            public ExtElementUnit ExU;
            public ExElmCommFunctionData(bool CheckVertion, ExtElementUnit ExU)
            {
                this.CheckVertion = CheckVertion;
                this.ExU = ExU;
            }
        }

        void CommFunction_SincExtElm(MySqlCommOneTransaction cmd, object CommFunctionData, string userID, out object ResultData)
        {
            ExElmCommFunctionData cData = (ExElmCommFunctionData)CommFunctionData;
            ServExtElementData rData = new ServExtElementData();

            rData.OldExU = cData.ExU;
            ResultData = rData;

            // текущая версия внелемента
            long ServVertion = -1;
            cmd.Parameters.Clear();
            cmd.CommandText = "select ver from orgz_ext_items where user_id=" + userID + " and Name='" + cData.ExU.ExElm.Name + "'";
            object obj = cmd.ExecuteScalar();
            if (obj != null) ServVertion = Convert.ToInt64(obj.ToString());

            #region *** версия сервера старше или равна ******************
            // запись элемента на сервер
            if (ServVertion <= cData.ExU.ServVertion)
            {
                // если просто проверка версии и она актуальна
                if (cData.CheckVertion && ServVertion == cData.ExU.ServVertion)
                {
                    // возвращаем, что версия актуальна
                    rData.stat = ExElmResultStat.VertionOK;
                    return;
                }
                
                // запись
                cmd.Parameters.Clear();
                cmd.CommandText = "delete from orgz_ext_items where user_id=" + userID + " and name = '" + cData.ExU.ExElm.Name + "'";
                cmd.ExecuteNonQuery();

                cmd.Parameters.Clear();
                cmd.CommandText = "insert into orgz_ext_items (user_id, Name, source_data, Ver) values(" + userID + ", @Name, @source_data, @Ver)";
                cmd.Parameters.Add("@Name", cData.ExU.ExElm.Name);
                cmd.Parameters.Add("@source_data", cData.ExU.ExElm.GetSourceData());
                cmd.Parameters.Add("@Ver", ServVertion+1);
                cmd.ExecuteNonQuery();

                rData.ServVer = ServVertion + 1;
                rData.stat = ExElmResultStat.WriteOK;

            }
            #endregion *** версия сервера старше или равна  ******************

            #region *** версия сервера новее ******************
            // ответ, о том, что требуется синхронизация
            if (ServVertion > cData.ExU.ServVertion)
            {
                cmd.Parameters.Clear();
                cmd.CommandText = "select Source_Data from orgz_ext_items where user_id=" + userID + " and Name='" + cData.ExU.ExElm.Name + "'";
                DataRowCollection rdr = cmd.ExecuteReader();
                if (rdr.Count > 0)
                {
                    rData.ServVer = ServVertion;
                    rData.SourceData = (byte[])rdr[0]["Source_Data"];
                    rData.stat = ExElmResultStat.NewVertion;
                }
                else throw new Exception("Не удалось найти запись в базе");
            }
            #endregion *** версия сервера новее ******************

        }
               
        #endregion *** Внелементы *******************************

        #region *** настройки ***********************************************

        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void FormService_FormClosing(object sender, FormClosingEventArgs e)
        {
            // проверки
            string login = txbLogin.Text.Trim();
            string pass = txbPass.Text.Trim();
            if (!StringIsCorrect(login))
            {
                e.Cancel = MessageBox.Show("В строке логина присутствуют недопустимые символы. Закрыть без сохранения?", "Ошибка ввода", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == System.Windows.Forms.DialogResult.No;
                return;
            }
            if (txbPass.Modified && !StringIsCorrect(pass))
            {
                e.Cancel = MessageBox.Show("В строке пароля присутствуют недопустимые символы. Закрыть без сохранения?", "Ошибка ввода", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == System.Windows.Forms.DialogResult.No;
                return;
            }

            Gl.sett.Sql_URL = txbSqlUrl.Text.Trim();
            Gl.sett.Sql_DBName = txbSqlDBName.Text.Trim();
            Gl.sett.Sql_Login = txbSqlLogin.Text.Trim();
            if (txbSqlPass.Modified) Gl.sett.Sql_Pass = txbSqlPass.Text.Trim();

            Gl.sett.Login = login;
            if (txbPass.Modified) { Gl.sett.PassMd5 = ESpace.PHPService.GetMD5(pass); }
            Gl.sett.Save();
        }

        bool StringIsCorrect(string str)
        {
            if (str == "") return false;
            for (int i = 0; i < str.Length; i++)
            {
                if (str[i] >= 48 && str[i] <= 57) continue;// цифра
                if (str[i] >= 97 && str[i] <= 122) continue; // буква англ строчн
                if (str[i] >= 65 && str[i] <= 90) continue; // буква англ заглавн
                if (str[i] >= 42 && str[i] <= 46) continue; // некоторые спец-символы
                if (str[i] >= 1040 && str[i] <= 1103) continue; // буквы русские
                if (str[i] == 95 || str[i] == 1105 || str[i] == 1025) continue; // символ '_' и буквы 'ё' 'Ё'

                return false;
            }
            return true;
        }

        private void txbPass_TextChanged(object sender, EventArgs e)
        {
            if (StringIsCorrect(txbPass.Text.Trim())) txbPass.BackColor = SystemColors.Window;
            else txbPass.BackColor = Color.Salmon;
        }

        private void txbPass_Enter(object sender, EventArgs e)
        {
            if (!txbPass.Modified) { txbPass.Text = ""; txbPass.Modified = false; }
        }

        private void txbPass_Leave(object sender, EventArgs e)
        {
            if (txbPass.Modified == false) { txbPass.Text = "**********"; txbPass.Modified = false; }
        }

        private void txbLogin_TextChanged(object sender, EventArgs e)
        {
            if (StringIsCorrect(txbLogin.Text.Trim())) txbLogin.BackColor = SystemColors.Window;
            else txbLogin.BackColor = Color.Salmon;
        }

        private void txbSqlPass_TextChanged(object sender, EventArgs e)
        {
            //if (StringIsCorrect(txbSqlPass.Text.Trim())) txbSqlPass.BackColor = SystemColors.Window;
            //else txbSqlPass.BackColor = Color.Salmon;
        }

        private void txbSqlPass_Enter(object sender, EventArgs e)
        {
            if (!txbSqlPass.Modified) { txbSqlPass.Text = ""; txbSqlPass.Modified = false; }
        }

        private void txbSqlPass_Leave(object sender, EventArgs e)
        {
            if (txbSqlPass.Modified == false) { txbSqlPass.Text = "**********"; txbSqlPass.Modified = false; }
        }

        #endregion *** настройки ***********************************************

    }
}
